#ifndef __KEYWORD_H
#define __KEYWORD_H

#include <map>
#include <vector>
#include <set>
#include <wx/string.h>
#include "debug.h"

using std::vector;
using std::map;
using std::set;

/// Checks single keyword for invalid characters.
/// @Return true if keyword is valid
bool CheckKeyword(const wxString& keyword);

/// Checks comma (or EOL) separated list of keywords for invalid characters or invalid syntax
/// @Param extra_chars Additional characters to be allowed in keywords.
/// @Return true if keyword is valid
bool CheckKeywordsString(const wxString& keywords, const wxString& extra_chars=_T(""));

/// Extract keywords from comma-separated string.
bool KeywordsFromString(const wxString& arg_from, vector<wxString>& keywords, const wxString& extra_chars=_T(""));

//==========================================================================================
/// Class holding description of single keyword
class Keyword
{
public:

    /// Unique keyword ID, assigned by DB engine.
    /// It can be 0 for new keyword, before it is written to DB.
    int       id;

    /// Canonical keyword name one that should be used in notes. Must be single line.
    /// Can't be empty, must be valid according to CheckKeyword().
    /// eg. 'Otto Klein-Krajnak'
    wxString name;

    /// Name in sorting order (last name, first name, title order). Must be single line.
    /// Can't be empty. Can be anything.
    /// eg. 'Klein-Krajnak, Otto'
    wxString sortname;

    /// Comma-separated list of aliases for this keyword.
    /// Purpose: When user searches for one of these aliases (incompatible with canonical name),
    /// he gets a warning to search for canonical name instead.
    /// eg. 'Otto Klein, Otto Krajnak, Oto Krajnak, Stefan Orlicky, Ludovit Kovac, Ludovit Kovacs, Peter, Viktor'
    wxString aliases;

    /// Comma-separated list of hints. When hint ends with "-", variants with all known suffixes are used.
    /// Purpose: When user types hint in note text, we can automatically find according keyword(s).
    /// eg. 'Klein-, Krajnak-, Peter, Viktor' (works also for 'Kleinovi', 'Krajnakov', etc.)
    /// Keyword name is hint by default.
    wxString hints;

    /// Description of keyword. Can be anything. Multi-line.
    /// eg. '17.8.1911, Vieden - 22.2.1973, Bratislava
    /// Komunista'
    wxString desc;

    /// Timestamp of keyword creation
    long created;
    void Created();

    /// Timestamp of last keyword modification
    long modified;
    void Modified();

    /// Timestamp of last keyword usage.
    /// Creation or modofication also counts as usage.
    long used;
    void Used();

    /// Constructor for new empty keyword.
    Keyword() {
        this->id = 0;
        this->name=L"";
        this->aliases=L"";
        this->hints=L"";
    }

    /// Constructor for known values. Does not allow blank values.
    Keyword(int id, const wxString& name, const wxString& sortname, const wxString& aliases, const wxString& hints) {
        wxASSERT(id > 0);
        wxASSERT(CheckKeyword(name));
        wxASSERT(sortname.Find('\n')==-1);
        wxASSERT(CheckKeywordsString(aliases));
        wxASSERT(CheckKeywordsString(hints));
        this->id = id;
        this->name = name;
        this->sortname = sortname;
        this->aliases = aliases;
        this->hints = hints;
    }

    /// Stringify to single-line (for log)
    wxString ToString() const;
};

//==========================================================================================
/// Class mainting description of all keywords

class KeywordList
{
    /// Map of Keyword IDs.
    /// Value is index to 'keywords' vector.
    map<int, int> ids;

    /// Map of Keyword names.
    /// Keyword names are unique, so the value is single ID (not index to 'keywords' vector!).
    map<wxString, int> names;

    /// Map of all hints, Key is hint pattern string
    /// Value is set of IDs (not indexes in 'keywords' vector!)
    /// All hint variants are filled by AddKeyword() method.
    map<wxString, set<int> > hints;

    /// Unordered vector of keywords.
    /// Access by index
    vector<Keyword> keywords;

    /// Internal helper to get writeable keyword from ID
    Keyword& _KeywordById(int id) {
        map<int, int>::const_iterator ids_it = ids.find(id);
        wxASSERT(ids_it != ids.end());
        int ind = ids_it->second;
        wxASSERT(ind >= 0 && ind < (int)keywords.size());
        return keywords[ind];
    }

    /// Internal helper to convert set of keyword indexes to vector of keywords
    void KeywordsByIndex(vector<Keyword>& result, const set<int>& inds) const {
        wxASSERT(inds.size()>0);
        for (set<int>::const_iterator ind=inds.begin(); ind!=inds.end(); ind++) {
            wxASSERT(*ind >= 0 && *ind < (int)keywords.size());
            result.push_back(keywords[*ind]);
        }
    }

    /// Internal helper to convert set of keyword ids to vector of keywords
    void KeywordsByIds(vector<Keyword>& result, const set<int>& _ids) const {
        wxASSERT(_ids.size()>0);
        for (set<int>::const_iterator it=_ids.begin(); it!=_ids.end(); it++) {
            wxASSERT(ids.find(*it) != ids.end());
            int ind = ids.find(*it)->second;
            wxASSERT(ind >= 0 && ind < (int)keywords.size());
            result.push_back(keywords[ind]);
        }
    }

    /// Internal routine to regenerate all tables from 'keywords'.
    /// Was required when keyword is changed or removed, probably not used now.
    void RecreateTables();

public:

    /// Adds keywords
    void AddKeyword(const Keyword& k);

    /// get number of keywords, for easy enumeration
    int KeywordCount() const {return keywords.size();}

    /// get number of hint variants, for informational purposes only
    int HintVariantsCount() const {return hints.size();}

    /// const Keyword getter, for easy enumeration
    const Keyword& KeywordByIndex(int ind) const
    {
        wxASSERT(ind >= 0 && ind < (int)keywords.size());
        return keywords[ind];
    }

    /// get const Keyword from ID
    const Keyword& KeywordById(int id) const;

    /// Checks if list contains keyword with specified name
    bool ContainsName(const wxString& name)
    {
        return names.find(name) != names.end();
    }

    /// Find keywords by partial name
    bool KeywordsByPartialName(vector<Keyword>& result, const wxString& name) const;

    /// Find keywords by hint
    bool KeywordsByHint(vector<Keyword>& result, const wxString& hint) const;

    /// Find keywords by alias. Returns true if any keyword is found.
    /// Keywords where both alias and name matches are excluded.
    bool KeywordsByAlias(vector<Keyword>& result, const wxString& alias) const;

    /// Replace existing keyword.
    void ReplaceKeyword(int id, const Keyword& new_keyword);

    /// Remove existing keyword
    void RemoveKeyword(int id);

    /// Update keyword last-used timestamp
    void KeywordUsed(int id);
};

extern KeywordList keyword_list;

#endif
